package net.hserver.gateway.core;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import net.hserver.gateway.core.bean.Proxy;
import net.hserver.gateway.core.bean.RouterInfo;
import net.hserver.gateway.core.exception.GateWayException;
import net.hserver.gateway.core.node.Manager;
import net.hserver.gateway.core.node.ServerNodeManager;
import net.hserver.gateway.core.ssl.HttpsMapperSslContextFactory;
import net.hserver.gateway.core.statistics.SimpleStatistics;
import net.hserver.gateway.core.strategy.LoadBalancingStrategy;
import net.hserver.gateway.core.strategy.ServerNode;
import net.hserver.gateway.core.strategy.Strategy;
import top.hserver.core.ioc.IocUtil;
import top.hserver.core.server.util.HServerIpUtil;

import javax.net.ssl.SSLEngine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @author hxm
 */
@Slf4j
public class ProxyHandler {

    static Proxy builder(ChannelHandlerContext ctx, Object msg, Proxy proxy, FrontendHandler frontendHandler) {
        proxy.setMsg(msg);
        proxy.setCtx(ctx);
        proxy.setFrontendHandler(frontendHandler);
        if (msg instanceof HttpRequest) {
            proxy.setHttpRequest((HttpRequest) msg);
        }
        return proxy;
    }

    static Proxy proxyInfo(Proxy proxy) {
        Strategy<ServerNode> strategy = IocUtil.getBean(LoadBalancingStrategy.class);
        HttpRequest msg = (HttpRequest) proxy.getMsg();
        Manager manager = IocUtil.getBean(ServerNodeManager.class);
        RouterInfo routerInfo = manager.getRouterIdByUrl(msg.uri());
        ServerNode serverNode = null;
        switch (routerInfo.getStrategyType()) {
            case 轮询法:
                serverNode = strategy.polling(routerInfo.getRouterId());
                break;
            case 加权轮询法:
                serverNode = strategy.weightPolling(routerInfo.getRouterId());
                break;
            case 源地址哈希法:
                serverNode = strategy.hashConsistency(routerInfo.getRouterId(), HServerIpUtil.getClientIp(proxy.getCtx()));
                break;
            case 最小连接数法:
                serverNode = strategy.minimumConnection(routerInfo.getRouterId());
                break;
            default:
                serverNode = strategy.random(routerInfo.getRouterId());
                break;
        }
        proxy.setServerNode(serverNode);
        return proxy;
    }

    static Proxy statistics(Proxy proxy) {
        HttpRequest httpRequest = proxy.getHttpRequest();
        if (httpRequest != null) {
            //开始统计
            SimpleStatistics bean = IocUtil.getBean(SimpleStatistics.class);
            bean.uv(proxy.getCtx());
            bean.pv();
            bean.platform(httpRequest);
        }
        return proxy;
    }


    static Proxy filter(Proxy proxy) {
        return proxy;
    }


    static Proxy exception(Throwable throwable) {
        Proxy proxy = new Proxy();
        proxy.setThrowable(throwable);
        return proxy;
    }

    static void endGateWay(CompletableFuture<Proxy> fu, Proxy proxy, ChannelHandlerContext ctx, Object msg) {

        /**
         * 是否有异常
         */
        if (proxy.getThrowable() != null) {
            proxy.setCtx(ctx);
            //一定要释放，不释放就内存泄漏
            proxy.setMsg(msg);
            log.error(proxy.getThrowable().getMessage());
            IocUtil.getBean(GateWayOut.class).out(new GateWayException(HttpResponseStatus.SERVICE_UNAVAILABLE, "网关异常", proxy.getThrowable()), proxy);
        }

        if (proxy.getServerNode() == null) {
            IocUtil.getBean(GateWayOut.class).out(new GateWayException(HttpResponseStatus.SERVICE_UNAVAILABLE, "拒绝访问"), proxy);
        } else {
            if (proxy.getFrontendHandler().outboundChannel == null) {
                final Channel inboundChannel = proxy.getCtx().channel();
                Bootstrap b = new Bootstrap();
                b.group(inboundChannel.eventLoop());
                SSLEngine sslEngine = HttpsMapperSslContextFactory.getClientContext().createSSLEngine();
                sslEngine.setUseClientMode(true);
                b.channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ChannelPipeline pipeline = ch.pipeline();
                        if (proxy.getServerNode().getInetSocketAddress().getPort() == 443) {
                            pipeline.addFirst(new SslHandler(sslEngine));
                        }
                        pipeline.addLast(new HttpClientCodec());
                        pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                        pipeline.addLast(new BackendHandler(inboundChannel));
                    }
                });
                try {
                    final AtomicInteger count = new AtomicInteger(0);
                    ChannelFuture f = b.connect(proxy.getServerNode().getInetSocketAddress()).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isSuccess()) {
                                /**
                                 * 添加连接数
                                 */
                                IocUtil.getBean(ServerNodeManager.class).addConnect(proxy.getServerNode().getRouterId(), proxy.getServerNode().getNodeId());
                                proxy.getFrontendHandler().read(proxy, proxy.getMsg());
                            } else {
                                if (count.incrementAndGet() > proxy.getServerNode().getErrorNum()) {
                                    future.channel().close();
                                    /**
                                     * 对当前节点打标，标识程序不健康哦.
                                     */
                                    IocUtil.getBean(ServerNodeManager.class).markUnhealthy(proxy.getServerNode().getRouterId(), proxy.getServerNode().getNodeId());
                                    //检查备用服务器，替换上线.


                                    //输出相关错误数据
                                    IocUtil.getBean(GateWayOut.class).out(new GateWayException(HttpResponseStatus.GATEWAY_TIMEOUT, "网关连接远端服务器超时", future.cause()), proxy);
                                } else {
                                    b.connect(proxy.getServerNode().getInetSocketAddress()).addListener(this);
                                }
                            }
                        }
                    });
                    proxy.getFrontendHandler().outboundChannel = f.channel();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                proxy.getFrontendHandler().read(proxy, proxy.getMsg());
            }
        }
        fu.complete(null);
    }
}
